//! MIR module generator from layouts.

use std::collections::HashMap;

use thiserror::Error;

use stencil_layout::{
    ArrayShape, BufContainerLayout, FieldLayout, FieldLayoutEntry, FieldLocator, FieldShape,
    FieldTopology, FieldType, ModTypedef, ModTypedefEntry, ModuleDef, SequenceFieldLayout,
    SequenceLenDesc, Typedef,
};
use stencil_tysys::{ArgRef, BinOp, IntEncoding, IntType, LiteralValue, UserTypeId, ValueType};
use stencil_util::{FieldName, FunctionName, IdentString, TypeName};

use crate::{
    container::ContainerFamily, AbortReason, AssignTarget, FieldEntry, MirBlock, MirStatement,
    TaggedExpr, TypedExpr,
};

use super::{
    function::MirFunction,
    module::{ContFunctionEntry, MirModule, ModContainerEntry},
    ContainerData, Expr,
};

#[derive(Debug, Error)]
pub enum GenError {
    #[error("unknown type '{0:?}'")]
    UnknownType(TypeName),

    #[error("unknown type '{0:?}'")]
    UnknownField(FieldName),

    #[error("function '{0:?}' already exists")]
    FunctionExists(FunctionName),

    #[error("insufficient args ({0}) for field topology ({1:?})")]
    InsufficientArgsForTopo(usize, FieldTopology),

    #[error("insufficient args ({0}) for array index ({1})")]
    InsufficientArgsForArray(usize, usize),

    #[error("tried to generate an expression to index a unit array")]
    IndexUnitArray,

    #[error("unknown user type {0:?}")]
    UnknownUserType(UserTypeId),

    #[error("tried to generate accessor for void field")]
    VoidAccessor,

    #[error("not yet implemented")]
    Unimplemented,
}

/// Generates MIR modules from layout modules.
pub struct MirModuleGenerator {
    // TODO
}

impl MirModuleGenerator {
    pub fn new() -> Self {
        Self {}
    }
}

pub struct ModuleBuilder<'c> {
    module_def: &'c ModuleDef,
}

impl<'c> ModuleBuilder<'c> {
    pub fn new(module_def: &'c ModuleDef) -> Self {
        Self { module_def }
    }

    /// Finalizes the builder into a completed module.
    pub fn finalize(self) -> MirModule {
        unimplemented!()
    }

    pub fn iter_typedefs(&self) -> impl Iterator<Item = &'c ModTypedefEntry> {
        self.module_def.typedefs().iter()
    }

    /// Creates a builder for a new type.
    pub fn create_type_builder(&self, def: &'c ModTypedef) -> TypeBuilder<'c> {
        TypeBuilder::new(&self.module_def, def)
    }
}

pub struct TypeBuilder<'c> {
    module_def: &'c ModuleDef,
    type_def: &'c ModTypedef,

    container_data: ContainerData,
    functions: HashMap<FunctionName, MirFunction>,
}

impl<'c> TypeBuilder<'c> {
    pub fn new(module_def: &'c ModuleDef, type_def: &'c ModTypedef) -> Self {
        Self {
            module_def,
            type_def,
            container_data: gen_container_data(type_def),
            functions: HashMap::new(),
        }
    }

    /// Finalizes the builder into a completed type entry.
    pub fn finalize(self) -> ModContainerEntry {
        let fns = self
            .functions
            .into_iter()
            .map(|(name, imp)| ContFunctionEntry::new(name, imp))
            .collect();
        ModContainerEntry::new(self.type_def.name().clone(), self.container_data, fns)
    }

    /// Adds a function to the type if it doesn't already exist.
    pub fn add_function(&mut self, name: FunctionName, imp: MirFunction) -> Result<(), GenError> {
        if self.functions.contains_key(&name) {
            return Err(GenError::FunctionExists(name));
        }

        self.functions.insert(name, imp);
        Ok(())
    }
}

/// Generates the container data for the layout type.
fn gen_container_data(td: &ModTypedef) -> ContainerData {
    match td {
        ModTypedef::Native(td) => {
            let mut fields = Vec::new();
            for td_field in td.fields() {
                let ty = td_field.shape().unit_ty().into(); // FIXME proper arrays
                fields.push(FieldEntry::new(td_field.name().clone(), ty));
            }
            ContainerData::new(ContainerFamily::Generic, fields)
        }

        ModTypedef::StructBuf(td) => {
            // These under the hood are always a single array.
            let buf_field = FieldEntry::new(
                FieldName::from(IdentString::new_from_snake("buf".to_owned()).unwrap()),
                ValueType::ByteArray(td.buf_size() as usize),
            );
            let fields = vec![buf_field];
            ContainerData::new(ContainerFamily::BufWrapper, fields)
        }
    }
}

/// Generates expressions involving reading from a buffer within a buf
/// container.
pub struct BufContExprGenerator<'c> {
    /// General context for information about other types.
    ctx: &'c LayoutContext,

    /// Layout information for the type we're generating.
    ty_layout: &'c BufContainerLayout,

    /// Assignment target when used when writing to the buffer.
    buf_target: AssignTarget,

    /// Expression that evals to the buffer we're reading from.
    buf_expr: TypedExpr,
}

/// Context type that contains information about the overall struct layouts.
pub struct LayoutContext {
    // TODO
}

impl LayoutContext {
    /// Gets the size of a buf struct.
    pub fn get_struct_size(&self, id: &UserTypeId) -> Option<usize> {
        eprintln!("mir/generator: don't support user type struct fields yet");
        None
    }

    /// Computes the size of a buf struct field.
    fn compute_field_ty_size(&self, fty: &FieldType) -> Result<usize, GenError> {
        Ok(match fty {
            FieldType::Void => 0,
            FieldType::Bool => 1,
            FieldType::Int(ienc) => ienc.ty().byte_len(),
            FieldType::Buf(len) => *len,
            FieldType::Struct(id) => self
                .get_struct_size(id)
                .ok_or(GenError::UnknownUserType(*id))?,
            FieldType::Array(len, inner) => len * self.compute_field_ty_size(inner.as_ref())?,
        })
    }
}

impl<'c> BufContExprGenerator<'c> {
    pub fn new(
        ctx: &'c LayoutContext,
        ty_layout: &'c BufContainerLayout,
        buf_target: AssignTarget,
        buf_expr: TypedExpr,
    ) -> Self {
        Self {
            ctx,
            ty_layout,
            buf_target,
            buf_expr,
        }
    }

    /// Finds the field layout entry with some name, if it exists, otherwise
    /// errors.
    fn find_field_entry_ok(&self, fname: &FieldName) -> Result<&FieldLayoutEntry, GenError> {
        self.ty_layout
            .find_field_entry(fname)
            .ok_or(GenError::UnknownField(fname.clone()))
    }

    /// Generates an expression that reads an int at some expression location.
    pub fn gen_read_int(&self, loc_expr: TypedExpr, ienc: IntEncoding) -> TypedExpr {
        TypedExpr::new_read_int(self.buf_expr.clone(), loc_expr, ienc)
    }

    /// Generates an expression that reads an int at some byte offset.
    pub fn gen_read_int_at(&self, off: u64, ienc: IntEncoding) -> TypedExpr {
        self.gen_read_int(TypedExpr::new_usize(off), ienc)
    }

    /// Generates an expression that reads the value of a field from the backing
    /// buffer.
    pub fn gen_read_field(&self, fname: &FieldName) -> Result<TypedExpr, GenError> {
        let fent: &FieldLayoutEntry = self.find_field_entry_ok(fname)?;

        let loc_expr = self.gen_field_loc_pos_expr(fent.layout().loc())?;
        let ienc = IntType::U32.to_big_encoding(); // FIXME
        Ok(self.gen_read_int(loc_expr, ienc))
    }

    pub fn gen_field_pos_expr(&self, fname: &FieldName) -> Result<TypedExpr, GenError> {
        let entry = self.find_field_entry_ok(fname)?;
        self.gen_field_loc_pos_expr(entry.layout().loc())
    }

    /// Generates an expression that computes the byte offset of a field location.
    pub fn gen_field_loc_pos_expr(&self, loc: &FieldLocator) -> Result<TypedExpr, GenError> {
        match loc {
            FieldLocator::Static(pos) => Ok(TypedExpr::new_usize(*pos)),
            FieldLocator::RefField(fname) => Ok(self.gen_read_field(fname)?),
            FieldLocator::AfterField(fname) => {
                // Compute the size of the preceeding and its length.
                let before_pos = self.gen_field_pos_expr(fname)?;
                let size_pos = self.gen_field_size_expr(fname)?;
                Ok(TypedExpr::Binop(
                    ValueType::USize,
                    BinOp::Add,
                    Box::new(before_pos),
                    Box::new(size_pos),
                ))
            }
        }
    }

    fn gen_field_size_expr(&self, fname: &FieldName) -> Result<TypedExpr, GenError> {
        let entry = self.find_field_entry_ok(fname)?;
        self.gen_field_layout_size_expr(entry.layout())
    }

    /// Generates an expression that computes the byte size of a field layout.
    fn gen_field_layout_size_expr(&self, layout: &FieldLayout) -> Result<TypedExpr, GenError> {
        match layout {
            FieldLayout::Unit(l) => {
                let ty_size = self.ctx.compute_field_ty_size(l.ty())?;
                Ok(TypedExpr::new_usize(ty_size as u64))
            }
            FieldLayout::Seq(l) => {
                // Compute the expression for the size of the chunk.
                let ty_size = self.ctx.compute_field_ty_size(l.unit_ty())?;
                let elems = l.shape().total_elements();
                let chunk_size_expr = TypedExpr::new_usize(ty_size as u64 * elems);

                match l.len() {
                    SequenceLenDesc::Fixed => Ok(chunk_size_expr),
                    SequenceLenDesc::Variable(fname) => {
                        // If it's dependent on another field, read that field
                        // and multiply the chunk size by it.
                        let len_expr = self.gen_read_field(fname)?;
                        Ok(TypedExpr::Binop(
                            ValueType::USize,
                            BinOp::Mul,
                            Box::new(len_expr),
                            Box::new(chunk_size_expr),
                        ))
                    }
                }
            }
        }
    }

    /// Generates an expression for computing a field location.
    fn gen_locator_expr(&self, loc: &FieldLocator) -> Result<TypedExpr, GenError> {
        match loc {
            FieldLocator::Static(off) => Ok(TypedExpr::new_usize(*off)),
            FieldLocator::RefField(field) => self.gen_read_field(field),
            FieldLocator::AfterField(field) => {
                // This might end up computing to be a const, we can const
                // propagate that later.
                let foff = self.gen_field_pos_expr(field)?;
                let flen = self.gen_field_size_expr(field)?;
                Ok(TypedExpr::Binop(
                    ValueType::USize,
                    BinOp::Add,
                    Box::new(foff),
                    Box::new(flen),
                ))
            }
        }
    }

    /// Generates an expression for computing a sequence's length.
    fn gen_seq_len_desc_expr(&self, desc: &SequenceLenDesc) -> Result<TypedExpr, GenError> {
        match desc {
            SequenceLenDesc::Fixed => Ok(TypedExpr::new_usize(1)),
            SequenceLenDesc::Variable(field) => self.gen_read_field(field),
        }
    }

    /// Generates bounds check statements that can be included at the beginning of a block.
    fn gen_bounds_checks_stmts(
        &self,
        layout: &SequenceFieldLayout,
        idx_args: &[TypedExpr],
    ) -> Result<Vec<MirStatement>, GenError> {
        if layout.index_args() != idx_args.len() {
            return Err(GenError::InsufficientArgsForArray(
                idx_args.len(),
                layout.index_args(),
            ));
        }

        let mut stmts = Vec::new();

        let const_idx_args = if layout.is_variable_size() {
            // Here we emit the sequence check.
            let seq_len_expr = self.gen_seq_len_desc_expr(layout.len())?;
            let seq_idx_expr = idx_args[0].clone();
            let ge_expr = TypedExpr::Binop(
                ValueType::Bool,
                BinOp::Ge,
                Box::new(seq_idx_expr),
                Box::new(seq_len_expr),
            );

            // Construct and save the statement.
            stmts.push(gen_abort_if_stmt(ge_expr, AbortReason::Oob));

            // Then move over the base of the list.
            &idx_args[1..]
        } else {
            idx_args
        };

        // TODO verify my array index ordering here is sensible.
        for (arg_expr, dim_len) in const_idx_args.iter().zip(layout.shape().dims_iter()) {
            let dim_expr = TypedExpr::new_usize(dim_len);

            let ge_expr = TypedExpr::Binop(
                ValueType::Bool,
                BinOp::Ge,
                Box::new(arg_expr.clone()),
                Box::new(dim_expr),
            );

            stmts.push(gen_abort_if_stmt(ge_expr, AbortReason::Oob));
        }

        Ok(stmts)
    }

    /// Generates an expression that evals to the position of a unit (sub)field
    /// we want to access, potentially using some index arguments.
    fn gen_layout_unit_loc_expr(
        &self,
        layout: &FieldLayout,
        idx_args: &[TypedExpr],
    ) -> Result<TypedExpr, GenError> {
        let base_off_expr = self.gen_field_loc_pos_expr(layout.loc())?;

        match layout {
            FieldLayout::Unit(_) => {
                if !idx_args.is_empty() {
                    // FIXME not exactly descriptive error here
                    return Err(GenError::IndexUnitArray);
                }

                Ok(base_off_expr)
            }

            FieldLayout::Seq(lo) => {
                let unit_size = self.ctx.compute_field_ty_size(lo.unit_ty())?;
                let unit_size_expr = TypedExpr::new_usize(unit_size as u64);

                // Depending on if it's a variable sequence or not we have to do some other handling here.
                let full_flat_idx_expr = if lo.is_variable_size() {
                    let dim0_stride = lo.shape().depth_stride_size(0);

                    // Construct the expression for the variable part.
                    let seq_expr = idx_args[0].clone();
                    let dim0_stride_expr = TypedExpr::Binop(
                        ValueType::USize,
                        BinOp::Mul,
                        Box::new(seq_expr),
                        Box::new(TypedExpr::new_usize(dim0_stride)),
                    );

                    // Finally put it together.
                    let const_flat_idx_expr = gen_array_flattened_idx(lo.shape(), &idx_args[1..])?;
                    TypedExpr::Binop(
                        ValueType::USize,
                        BinOp::Add,
                        Box::new(dim0_stride_expr),
                        Box::new(const_flat_idx_expr),
                    )
                } else {
                    gen_array_flattened_idx(lo.shape(), idx_args)?
                };

                // Multiply the unit size by the flat index.
                let subfield_off_expr = TypedExpr::Binop(
                    ValueType::USize,
                    BinOp::Mul,
                    Box::new(full_flat_idx_expr),
                    Box::new(unit_size_expr),
                );

                // Add the two offsets.
                let off_expr = TypedExpr::Binop(
                    ValueType::USize,
                    BinOp::Add,
                    Box::new(base_off_expr),
                    Box::new(subfield_off_expr),
                );

                Ok(off_expr)
            }
        }
    }

    /// Generates a block for a field unit setter function.
    fn gen_field_unit_setter(
        &self,
        layout: &FieldLayout,
        val_expr: TypedExpr,
        idx_exprs: &[TypedExpr],
    ) -> Result<MirBlock, GenError> {
        unimplemented!()
    }

    /// Generates a block for a field unit getter function.
    fn gen_field_unit_getter(
        &self,
        layout: &FieldLayout,
        idx_exprs: &[TypedExpr],
    ) -> Result<MirBlock, GenError> {
        unimplemented!()
    }
}

/// Generates a setter for a buffer field.
fn gen_buf_prim_field_setter(
    buf_target: AssignTarget,
    off_expr: TypedExpr,
    val_expr: TypedExpr,
    ty: &FieldType,
) -> Result<MirBlock, GenError> {
    let stmt = match ty {
        FieldType::Bool => {
            let byte_expr = TypedExpr::Cast(ValueType::Bool, ValueType::Bool, Box::new(val_expr));
            MirStatement::AssignBufInt(buf_target, off_expr, byte_expr, IntEncoding::byte())
        }

        FieldType::Int(ienc) => MirStatement::AssignBufInt(buf_target, off_expr, val_expr, *ienc),

        FieldType::Buf(len) => {
            eprintln!("mir/generator: buf field setters unsupported");
            return Err(GenError::Unimplemented);
        }

        FieldType::Void => return Err(GenError::VoidAccessor),

        FieldType::Struct(_) => {
            eprintln!("mir/generator: struct field setters unsupported");
            return Err(GenError::Unimplemented);
        }

        FieldType::Array(_, _) => {
            eprintln!("mir/generator: array field setters unsupported");
            return Err(GenError::Unimplemented);
        }
    };

    Ok(MirBlock::new(vec![stmt]))
}

/// Generates a getter for a buffer int field.
fn gen_buf_prim_field_getter(
    buf_expr: TypedExpr,
    off_expr: TypedExpr,
    ty: &FieldType,
) -> Result<MirBlock, GenError> {
    let val_expr = match ty {
        FieldType::Bool => {
            let read_expr = TypedExpr::BufferReadInt(
                ValueType::U8,
                Box::new(buf_expr),
                Box::new(off_expr),
                IntEncoding::byte(),
            );

            // This is just `val > 0`.
            TypedExpr::Binop(
                ValueType::Bool,
                BinOp::Gt,
                Box::new(read_expr),
                Box::new(TypedExpr::Const(ValueType::U8, LiteralValue::U8(0))),
            )
        }

        FieldType::Int(ienc) => TypedExpr::BufferReadInt(
            ValueType::from(ienc.ty()),
            Box::new(buf_expr),
            Box::new(off_expr),
            *ienc,
        ),

        FieldType::Buf(len) => {
            eprintln!("mir/generator: buf field getters unsupported");
            return Err(GenError::Unimplemented);
        }

        FieldType::Void => return Err(GenError::VoidAccessor),

        FieldType::Struct(_) => {
            eprintln!("mir/generator: struct field getters unsupported");
            return Err(GenError::Unimplemented);
        }

        FieldType::Array(_, _) => {
            eprintln!("mir/generator: array field getters unsupported");
            return Err(GenError::Unimplemented);
        }
    };

    // Then we wrap it up in a statement to return the value.
    let stmt = MirStatement::ReturnValue(val_expr);
    Ok(MirBlock::new(vec![stmt]))
}

/// Gets the encoded size in bytes of some unit type.
fn get_type_size(ty: &ValueType) -> Result<u64, GenError> {
    if let Some(ity) = IntType::try_from(ty).ok() {
        Ok(ity.byte_len() as u64)
    } else {
        let s = match ty {
            ValueType::Bool => 1,
            ValueType::ByteArray(len) => *len as u64,
            _ => {
                eprintln!("mir/generator: get_type_size of non-simple type");
                return Err(GenError::Unimplemented);
            }
        };

        Ok(s)
    }
}

/// Generates an expression that computes the offset within an array shape, using some index expressions.
fn gen_array_flattened_idx(
    shape: &ArrayShape,
    idx_exprs: &[TypedExpr],
) -> Result<TypedExpr, GenError> {
    // Safety checks.
    if idx_exprs.len() != shape.dimensions() {
        return Err(GenError::InsufficientArgsForArray(
            idx_exprs.len(),
            shape.dimensions(),
        ));
    }

    if shape.is_unit() {
        return Err(GenError::IndexUnitArray);
    }

    // Easy solution for most common case.
    if shape.dimensions() == 1 {
        return Ok(idx_exprs[0].clone());
    }

    // First generate expressions for each dimension.
    let mut sub_exprs: Vec<TypedExpr> = Vec::new();
    for (i, expr) in idx_exprs.iter().enumerate() {
        let depth = i + 1;
        let stride = shape.depth_stride_size(depth);

        if stride == 0 {
            panic!("mir/generator: zero stride?");
        }

        if stride > 1 {
            let stride_expr = TypedExpr::new_usize(stride);
            sub_exprs.push(TypedExpr::Binop(
                ValueType::USize,
                BinOp::Mul,
                Box::new(expr.clone()),
                Box::new(stride_expr),
            ));
        } else {
            sub_exprs.push(expr.clone());
        }
    }

    // Then we fold them together depending on how many we actually have.
    Ok(match sub_exprs.len() {
        0 => unreachable!(),
        1 => sub_exprs.remove(0),
        _ => {
            // Have to do this weird accumulation to make this work.
            let mut acc = sub_exprs.remove(0);
            for e in sub_exprs {
                acc = TypedExpr::Binop(ValueType::USize, BinOp::Add, Box::new(acc), Box::new(e));
            }

            acc
        }
    })
}

/// Constructs a statement to abort if a condition expression is true.
fn gen_abort_if_stmt(cond_expr: TypedExpr, reason: AbortReason) -> MirStatement {
    let raise_stmt = MirStatement::Abort(reason);
    let raise_block = MirBlock::new(vec![raise_stmt]);
    MirStatement::If(cond_expr, Box::new(raise_block))
}

#[cfg(test)]
mod tests {
    use stencil_layout::{
        ArrayShape, BufContainerLayout, FieldLayout, FieldLayoutEntry, FieldLocator, FieldType,
        SequenceFieldLayout, SequenceLenDesc, UnitFieldLayout,
    };
    use stencil_tysys::{ArgRef, EnvExpr, IntEncoding, IntType, LocalRef, ValueType};
    use stencil_util::{FieldName, IdentString};

    use crate::{AssignTarget, TypedExpr};

    use super::{gen_array_flattened_idx, BufContExprGenerator, LayoutContext};

    fn make_basic_layout_context() -> LayoutContext {
        // TODO
        LayoutContext {}
    }

    fn make_basic_cont_layout() -> BufContainerLayout {
        let fn1 = FieldName::from(IdentString::new_from_snake("foo".to_owned()).unwrap());
        let fl1 =
            UnitFieldLayout::new(FieldType::Int(IntEncoding::byte()), FieldLocator::Static(3));
        let f1 = FieldLayoutEntry::new_simple(fn1.clone(), FieldLayout::Unit(fl1));

        let fn2 = FieldName::from(IdentString::new_from_snake("bar".to_owned()).unwrap());
        let fl2 = UnitFieldLayout::new(
            FieldType::Int(IntEncoding::byte()),
            FieldLocator::AfterField(fn1.clone()),
        );
        let f2 = FieldLayoutEntry::new_simple(fn2.clone(), FieldLayout::Unit(fl2));

        let fn3 = FieldName::from(IdentString::new_from_snake("baz".to_owned()).unwrap());
        let fl3 = SequenceFieldLayout::new(
            FieldType::Int(IntType::I32.to_big_encoding()),
            FieldLocator::AfterField(fn2),
            ArrayShape::flat(5),
            SequenceLenDesc::Variable(fn1),
        );
        let f3 = FieldLayoutEntry::new_simple(fn3, FieldLayout::Seq(fl3));

        BufContainerLayout::new(vec![f1, f2, f3])
    }

    fn make_buf_cont_gen<'c>(
        ctx: &'c LayoutContext,
        ty_layout: &'c BufContainerLayout,
    ) -> BufContExprGenerator<'c> {
        BufContExprGenerator {
            ctx,
            ty_layout,
            buf_target: AssignTarget::Local(LocalRef::from(0)),
            buf_expr: TypedExpr::Env(ValueType::ByteSlice, EnvExpr::Local(LocalRef::from(0))),
        }
    }

    #[test]
    fn test_get_array_flattened_idx_basic() {
        let shape = ArrayShape::new_unit()
            .in_array_of_size(3)
            .in_array_of_size(5)
            .in_array_of_size(7)
            .in_array_of_size(11);

        let idx_exprs = &[
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(0))),
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(1))),
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(2))),
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(3))),
        ];

        let expr =
            gen_array_flattened_idx(&shape, idx_exprs).expect("test: get_array_flattened_idx");

        eprintln!("{expr:#?}");
    }

    #[test]
    fn test_gen_variable_field_pos_basic() {
        let ctx = make_basic_layout_context();
        let cont = make_basic_cont_layout();
        let gen = make_buf_cont_gen(&ctx, &cont);

        // Extract the field we want to inspect.
        let f3 = &cont.fields()[2];

        let idx_exprs = &[
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(0))),
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(1))),
        ];

        let expr = gen
            .gen_layout_unit_loc_expr(f3.layout(), idx_exprs)
            .expect("test: gen_layout_unit_loc_expr");

        eprintln!("{expr:#?}");
    }

    #[test]
    fn test_gen_bounds_checks_stmts_basic() {
        let ctx = make_basic_layout_context();
        let cont = make_basic_cont_layout();
        let gen = make_buf_cont_gen(&ctx, &cont);

        // Extract the field we want to inspect.
        let f3 = &cont.fields()[2];

        let FieldLayout::Seq(lo) = f3.layout() else {
            panic!("test: unexpected type");
        };

        let idx_exprs = &[
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(0))),
            TypedExpr::Env(ValueType::USize, EnvExpr::Arg(ArgRef::from(1))),
        ];

        let stmts = gen
            .gen_bounds_checks_stmts(lo, idx_exprs)
            .expect("test: gen_layout_unit_loc_expr");

        eprintln!("{stmts:#?}");
    }
}
